package com.gonsin.ta.logic.process;

import com.gonsin.ta.logic.db.SignalBean;
import com.gonsin.ta.logic.enums.TaResult;
import com.gonsin.ta.logic.event.TaInitAfterFinishedEvent;
import com.gonsin.ta.logic.event.TaInitBeforeFinishedEvent;
import com.gonsin.ta.logic.exceptions.TaException;
import com.gonsin.ta.logic.controllers.po.TaSignalPost;
import com.gonsin.ta.logic.db.mapper.SignalMapper;
import com.gonsin.ta.logic.parse.condition.*;
import com.gonsin.ta.logic.parse.condition.context.ChainContextImpl;
import com.gonsin.ta.logic.parse.condition.context.ChainContextPo;
import com.gonsin.ta.logic.parse.serialize.SerialResult;
import com.gonsin.ta.logic.parse.serialize.SerializeContext;
import com.gonsin.ta.logic.parse.serialize.TaDeserializable;
import com.gonsin.ta.logic.parse.serialize.TaSerializable;
import com.gonsin.ta.logic.process.quartz.TimeoutExecuteTask;
import com.gonsin.ta.logic.utils.TaJson;
import com.google.gson.GsonBuilder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 信号处理中心
 */
@Component
@Slf4j
public class SignalCenter implements ApplicationListener<TaInitBeforeFinishedEvent> {

    @Autowired private SignalMapper signalMapper;
    @Autowired(required = false) private SchedulerFactory schedulerFactory;

    @Autowired private ApplicationContext applicationContext;

    /**
     * 发出信号，控制流程
     */
    public TaResponse post(TaSignalPost signal){
        SignalBean bean = signalMapper.selectSignal(signal.getInstanceId(), signal.getSignal());
        if(bean == null){
            throw new TaException("再流程【{}】中，找不到该信号【{}】", signal.getInstanceId(), signal.getSignal());
        }


        // TODO 执行失败或成功是否删除该信号

        try {
//            ChainContextImpl context = TaJson.deSerialize(new String(bean.getContext(), StandardCharsets.UTF_8), ChainContextImpl.class);
            String json = new String(bean.getContext(), StandardCharsets.UTF_8);
            ChainContextPo po = TaJson.deSerialize(json, ChainContextPo.class);
            ChainContextImpl context = new ChainContextImpl(po);
            context.setApplicationContext(applicationContext);
            context.setResult(null);
            if(context.getValues() != null){
                context.getValues().putAll(signal.getValues());
            } else {
                context.setValues(signal.getValues());
            }
            List<Executable> next = ExecutableSerializeHelper.createExecutableFromJson(bean.getNext(), bean.getStack(), this);
            for(Executable e : next){
                e.execute(context);
            }
            signalMapper.deleteSignal(bean.getSignalId());

            // TODO 此处防止超时任务和原任务线程冲突
            if(context.getResult() == null){
                context.setResult(TaResult.FINISHED);
            }

            // 删除定时任务
            deleteTimeoutTask(bean.getSignalId());
            TaResponse response = new TaResponse();
            response.setInstanceId(context.getInstanceId());
            response.setContext(context);
            response.setResult(context.getResult());
            return response;
        } catch (Exception ex) {
            log.error(ex.toString(), ex);
            TaResponse response = new TaResponse();
            response.setResult(TaResult.FAIL);
            return response;
        }
    }


    /**
     * 添加一个信号记录
     * @param context 原来的上下文
     * @param signal 信号字符串
     * @param timeout 超时时间
     * @param next
     * @param timeoutException
     */
    public void addSignal(ChainContextImpl context, String signal, Long timeout, List<TaDeserializable> next, List<TaDeserializable> timeoutException, Map<String, TaDeserializable> stack) {


        // 添加信号到数据库
        SignalBean bean = new SignalBean();
        bean.setSignal(signal);
        bean.setSignalId(RandomStringUtils.random(32, true, true));
        bean.setInstanceId(context.getInstanceId());
        bean.setInsertTime(System.currentTimeMillis());
        bean.setTimeout(timeout);
        try {
//            bean.setContext(JDKSerializationUtil.serialize(context));
            bean.setContext(TaJson.serialize(context.getContextPo()).getBytes(StandardCharsets.UTF_8));
            bean.setNext(new GsonBuilder().create().toJson(next));
            if(timeoutException != null){
                bean.setTimeoutTask(new GsonBuilder().create().toJson(timeoutException));
            }
            bean.setStack(new GsonBuilder().create().toJson(stack));
        } catch (Exception e) {
            log.error(e.toString(), e);
            throw new TaException("无法序列化对象", e);
        }
        signalMapper.insertSignal(bean);

        // 建立超时任务
        if(timeout != null && timeoutException != null){
            pushTimeoutTask(bean);
        }
    }

    @Override
    public void onApplicationEvent(TaInitBeforeFinishedEvent event) {

        if(schedulerFactory == null){
            log.error("请参考例子配置 org.quartz.SchedulerFactory ，否则无法启动");
            ((ConfigurableApplicationContext)event.getApplicationContext()).close();
            return;
        }

        List<SignalBean> signals = signalMapper.selectAllTimeout();
        if(CollectionUtils.isEmpty(signals)){
            return;
        }

        long now = System.currentTimeMillis();
        for(SignalBean bean : signals){

            // 如果信号超时，直接执行
            if(bean.getTimeout() != null && bean.getTimeout() != 0){
                long validate = bean.getInsertTime() + bean.getTimeout();
                if(now > validate){
                    try {
//                    ChainContextImpl context = TaJson.deSerialize(new String(bean.getContext(), StandardCharsets.UTF_8), ChainContextImpl.class);
                        String json = new String(bean.getContext(), StandardCharsets.UTF_8);
                        ChainContextPo po = TaJson.deSerialize(json, ChainContextPo.class);
                        ChainContextImpl context = new ChainContextImpl(po);
                        context.setApplicationContext(applicationContext);
                        List<Executable> next = ExecutableSerializeHelper.createExecutableFromJson(bean.getTimeoutTask(), bean.getStack(), this);
                        for (Executable e : next) {
                            e.execute(context);
                        }
                    } catch (Exception ex){
                        log.error("TA - 执行超时任务错误，该错误不会影响正常启动，并且只会提示一次", ex);
                        signalMapper.deleteSignal(bean.getSignalId());
                    }

                    // 如果信号未超时，则开启定时任务
                } else {
                    pushTimeoutTask(bean);
                }
            }

        }

        // 抛出初始化完成事件
        TaBus.APPLICATION_CONTEXT = event.getApplicationContext();
        event.getApplicationContext().publishEvent(new TaInitAfterFinishedEvent(event.getApplicationContext()));
    }

    /**
     * 删除定时任务
     */
    @SneakyThrows
    private void deleteTimeoutTask(String signalId) {
        String jobName = signalId + "-timeout";
        schedulerFactory.getScheduler()
                .deleteJob(JobKey.jobKey(jobName));
    }

    /**
     * 设置超时任务
     */
    private void pushTimeoutTask(SignalBean bean) {
        try {
            String jobName = bean.getSignalId() + "-timeout";

            Scheduler scheduler = schedulerFactory.getScheduler();
            if(scheduler.checkExists(JobKey.jobKey(jobName))){
                return;
            }

            if(bean.getTimeout() != null && bean.getTimeout() != 0){
                long validate = bean.getInsertTime() + bean.getTimeout();
                Map<String, Object> params = new HashMap<>();
                params.put("context", bean.getContext());
                params.put("timeoutTask", bean.getTimeoutTask());
                params.put("stack", bean.getStack());
                params.put("signalCenter", this);
                params.put("signalId", bean.getSignalId());
                Date delayDate = new Date(validate);

                JobDetail jobDetail = JobBuilder.newJob(TimeoutExecuteTask.class)
                        .withIdentity(jobName)// 任务名称和组构成任务key
                        .usingJobData(new JobDataMap(params))
                        .build();
                // 定义调度触发规则
                Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName)// 触发器key
                        .startAt(delayDate)
                        .usingJobData(new JobDataMap(params))
                        .build();

                // 把作业和触发器注册到任务调度中
                scheduler.scheduleJob(jobDetail, trigger);
                // 启动
                if (!scheduler.isShutdown()) {
                    scheduler.start();
                }
            }

        } catch (Exception e) {
            log.error("TA开启定时任务失败");
            log.error(e.toString(), e);
        }
    }

    /**
     * 超时任务执行完之后，自动删除信号值
     */
    public void deleteSignal(String signalId) {
        signalMapper.deleteSignal(signalId);
    }


    /**
     * 序列化可执行任务
     */
    public SerialResult serialize(List<Executable> serializable) {

        SerializeContext context = new SerializeContext();

        List<TaDeserializable> deserializable = new ArrayList<>();
        for(TaSerializable s : serializable){
            if(s == null){
                continue;
            }
            TaDeserializable de = s.toSerializable(context);
            if(de == null){
                continue;
            }
            deserializable.add(de);
        }
        SerialResult result = new SerialResult();
        result.setExecutable(deserializable);
        result.setStack(context.getFutureDeserializable());
        return result;
    }
}
